
#include <ddk.h>
#include "CrtFileIO.h"

using namespace Elastos;

#include "uffs/uffs_device.h"

#define PFX "femu:"

#define printf kprintf

extern void * FsKmalloc(UInt32 n);
extern void FsKfree(void *add);

#define SEEK_SET 0

#define DEFAULT_EMU_FILENAME "uffsemufile.bin"
const char * conf_emu_filename  = DEFAULT_EMU_FILENAME;

/* default basic parameters of the NAND device */
#if PAGE512B
int conf_pages_per_block = 32;
int conf_pages_data_size = 512;//1024;//512
int conf_pages_spare_size = 16;
int conf_total_blocks =	128;		//2M
#elif PAGE2K
int conf_pages_per_block = 64;
int conf_pages_data_size = 2048;
int conf_pages_spare_size = 64;
int conf_total_blocks =	1000;		//2M
#elif
#error "Need define PAGE SIZE"
#endif
int conf_sim_manid = MAN_ID_SAMSUNG;	//default: simulate Samsung's NAND flash

#define NANDFLASH_PAGE_SIZE			(conf_pages_data_size + conf_pages_spare_size)
#define BLOCK_DATA_SIZE				(conf_pages_per_block * conf_pages_data_size)
#define TOTAL_DATA_SIZE				(conf_total_blocks * BLOCK_DATA_SIZE)
#define BLOCK_SIZE					(conf_pages_per_block * NANDFLASH_PAGE_SIZE)
#define TOTAL_SIZE					(BLOCK_SIZE * conf_total_blocks)

#define MAXWRITETIME_PAGE 2
#define MAXWRITETIME_SPARE 2

#ifdef EM_MONITOR_WRITE
    static u8 * em_monitor_page = NULL;		//page write count
    static u8 * em_monitor_spare = NULL;	//page spare write count
#endif //EM_MONITOR_WRITE


static u8 em_page_buf[EM_PAGE_BUF];

static URET CheckInit(uffs_Device *dev);
static URET femu_WritePage(uffs_Device *dev, u32 block, u32 pageNum, const u8 *data, const u8 *spare);
static URET femu_WritePageData(uffs_Device *dev, u32 block, u32 pageNum, const u8 *page, int ofs, int len);
static URET femu_WritePageSpare(uffs_Device *dev, u32 block, u32 pageNum, const u8 *spare, int ofs, int len);
static URET femu_ReadPage(uffs_Device *dev, u32 block, u32 pageNum, u8 *data, u8 *spare);
static URET femu_ReadPageData(uffs_Device *dev, u32 block, u32 pageNum, u8 *page, int ofs, int len);
static URET femu_ReadPageSpare(uffs_Device *dev, u32 block, u32 pageNum, u8 *spare, int ofs, int len);
static URET femu_EraseBlock(uffs_Device *dev, u32 blockNumber);
static URET femu_ResetFlash(uffs_Device *dev);
static UBOOL femu_IsBlockBad(uffs_Device *dev, u32 block);
static URET femu_initDevice(uffs_Device *dev);
static URET femu_releaseDevice(uffs_Device *dev);

typedef struct femu_privateSt {
	int initCount;
	FILE *fp;
} femu_private;

static femu_private g_femu_priv = {0};

static URET CheckInit(uffs_Device *dev)
{
	int i;
	int fSize;
	int written;
	u8 * p = em_page_buf;
	femu_private *emu;

	int pgSize, pgdSize, spSize, blks, blkPgs, blkSize;
	pgSize = dev->attr.page_data_size + dev->attr.spare_size;
	pgdSize = dev->attr.page_data_size;
	spSize = dev->attr.spare_size;
	blkPgs = dev->attr.pages_per_block;
	blks = dev->attr.total_blocks;
	blkSize = dev->attr.block_data_size;
//	printf("-------CheckInit------\n");
	emu = (femu_private *)(dev->_private);

	if(emu->initCount > 0) {
		emu->initCount++;
		return U_SUCC;
	}

	//clear monitor
#ifdef EM_MONITOR_WRITE
	memset(em_monitor_page, 0, blks * blkPgs);
	memset(em_monitor_spare, 0, blks * blkPgs);
#endif //EM_MONITOR_WRITE

	emu->fp = my_fopen(conf_emu_filename, "rb");
	if (emu->fp == NULL) {
		emu->fp = my_fopen(conf_emu_filename, "ab+");
		if (emu->fp == NULL) {
			printf(PFX"Failed to create uffs emulation file.");
			return U_FAIL;
		}

		//fseek(emu->fp, 0, SEEK_END);
		fSize = my_ftell(emu->fp);

		if(fSize < blkSize * blks)	{
			printf("Creating uffs emulation file.p=0x%08x\n", emu->fp);
			//__asm("int $3");
			my_fseek(emu->fp, 0, SEEK_SET);
			memset(p, 0xff, pgdSize + spSize);
			for(i = 0; i < blkPgs * blks; i++) {
				written = my_fwrite(p, 1, pgdSize + spSize, emu->fp);
				if(written != pgdSize + spSize)	{
					kprintf("Write failed\n");
					my_fclose(emu->fp);
					emu->fp = NULL;
					return U_FAIL;
				}
			}
		}
	}
	my_fflush(emu->fp);
	my_fclose(emu->fp);

    kprintf("UffsEmulator.cpp:my_fopen(conf_emu_filename, rb+)\n");
	emu->fp = my_fopen(conf_emu_filename, "rb+");
	if (emu->fp == NULL) {
		kprintf(PFX"Can't open emulation file.\n");
		return U_FAIL;
	}

	emu->initCount++;

	return U_SUCC;
}

static URET femu_WritePage(uffs_Device *dev, u32 block, u32 pageNum, const u8 *data, const u8 *spare)
{
	int ret;
	int pgSize, pgdSize, spSize, blks, blkPgs, blkSize;

	pgSize = dev->attr.page_data_size + dev->attr.spare_size;
	pgdSize = dev->attr.page_data_size;
	spSize = dev->attr.spare_size;
	blkPgs = dev->attr.pages_per_block;
	blks = dev->attr.total_blocks;
	blkSize = dev->attr.block_data_size;

	if(data) {
		ret = femu_WritePageData(dev, block, pageNum, data, 0, pgdSize);
		if(ret == U_FAIL) return ret;
	}

	if(spare) {
		ret = femu_WritePageSpare(dev, block, pageNum, spare, 0, spSize);
		if(ret == U_FAIL) return ret;
	}

	return U_SUCC;

}

static URET femu_WritePageData(uffs_Device *dev, u32 block, u32 pageNum, const u8 *page, int ofs, int len)
{
	int written;
	int pgSize, pgdSize, spSize, blks, blkPgs, blkSize;
	femu_private *emu;
	emu = (femu_private *)(dev->_private);

//   printf("-------femu_WritePageData------\n");
	if (!emu || !(emu->fp)) return U_FAIL;

	pgSize = dev->attr.page_data_size + dev->attr.spare_size;
	pgdSize = dev->attr.page_data_size;
	spSize = dev->attr.spare_size;
	blkPgs = dev->attr.pages_per_block;
	blks = dev->attr.total_blocks;
	blkSize = dev->attr.block_data_size;

	if(ofs + len > pgdSize) {
		printf("femu: write page data out of range!\n");
		return U_FAIL;
	}

#ifdef EM_MONITOR_WRITE
	em_monitor_page[block * blkPgs + pageNum]++;
	if(em_monitor_page[block * blkPgs + pageNum] > MAXWRITETIME_PAGE) {
		printf("Warrning: block %d page %d exceed it's maximum write time!\r\n", block, pageNum);
		return U_FAIL;
	}
#endif //EM_MONITOR_WRITE

	if(page)
	{
		my_fseek(emu->fp,
			(block * blkPgs + pageNum) *
			(pgdSize + spSize)
			+ ofs, SEEK_SET);
		written = my_fwrite(page, 1, len, emu->fp);

		if(written != len) {
			printf("femu: write page I/O error ?\n");
			return U_FAIL;
		}
	}

	dev->st.pageWriteCount++;
	my_fflush(emu->fp);

	return U_SUCC;
}

static URET femu_WritePageSpare(uffs_Device *dev, u32 block, u32 pageNum, const u8 *spare, int ofs, int len)
{
	int written;
	int pgSize, pgdSize, spSize, blks, blkPgs, blkSize;
	femu_private *emu;
	emu = (femu_private *)(dev->_private);

//    printf("-------femu_WritePageSpare------\n");
	if (!emu || !(emu->fp)) return U_FAIL;

	pgSize = dev->attr.page_data_size + dev->attr.spare_size;
	pgdSize = dev->attr.page_data_size;
	spSize = dev->attr.spare_size;
	blkPgs = dev->attr.pages_per_block;
	blks = dev->attr.total_blocks;
	blkSize = dev->attr.block_data_size;

//	printf("WS: %d/%d, size %d\n", block, pageNum, len);

	if(ofs + len > spSize) {
		printf("femu: write page data out of range!\n");
		return U_FAIL;
	}

#ifdef EM_MONITOR_WRITE
	em_monitor_spare[block*blkPgs + pageNum]++;
	if(em_monitor_spare[block*blkPgs + pageNum] > MAXWRITETIME_SPARE) {
		printf("Warrning: block %d page %d (spare) exceed it's maximum write time!\r\n", block, pageNum);
		return U_FAIL;
	}
#endif //EM_MONITOR_WRITE

	if(spare) {
		my_fseek(emu->fp, (block*blkPgs + pageNum) * (pgdSize + spSize) + conf_pages_data_size + ofs, SEEK_SET);
		written = my_fwrite(spare, 1, len, emu->fp);
		if(written != len) {
			printf("femu: write spare I/O error ?\n");
			return U_FAIL;
		}
	}

	my_fflush(emu->fp);
	dev->st.spareWriteCount++;

	return U_SUCC;
}


static URET femu_ReadPage(uffs_Device *dev, u32 block, u32 pageNum, u8 *data, u8 *spare)
{
	int ret;
	int pgSize, pgdSize, spSize, blks, blkPgs, blkSize;
	pgSize = dev->attr.page_data_size + dev->attr.spare_size;
	pgdSize = dev->attr.page_data_size;
	spSize = dev->attr.spare_size;
	blkPgs = dev->attr.pages_per_block;
	blks = dev->attr.total_blocks;
	blkSize = dev->attr.block_data_size;

	if(data)
	{
		ret = femu_ReadPageData(dev, block, pageNum, data, 0, pgdSize);
		if(ret == U_FAIL) return U_FAIL;
	}

	if(spare)
	{
		ret = femu_ReadPageSpare(dev, block, pageNum, spare, 0, spSize);
		if(ret == U_FAIL) return U_FAIL;
	}

	return U_SUCC;
}

static URET femu_ReadPageData(uffs_Device *dev, u32 block, u32 pageNum, u8 *page, int ofs, int len)
{
	int nread;
	int pgSize, pgdSize, spSize, blks, blkPgs, blkSize;
	femu_private *emu;
	emu = (femu_private *)(dev->_private);

//	printf("-------femu_ReadPage------\n");
	if (!emu || !(emu->fp))   return U_FAIL;

	pgSize = dev->attr.page_data_size + dev->attr.spare_size;
	pgdSize = dev->attr.page_data_size;
	spSize = dev->attr.spare_size;
	blkPgs = dev->attr.pages_per_block;
	blks = dev->attr.total_blocks;
	blkSize = dev->attr.block_data_size;

	if(ofs + len > pgdSize) {
		printf("femu: read page data out of range!\n");
		return U_FAIL;
	}

	if(page) {
		my_fseek(emu->fp, (block*blkPgs + pageNum) * (pgdSize + spSize) + ofs, SEEK_SET);
		nread = my_fread(page, 1, len, emu->fp);

		if(nread != len) {
			printf("femu: read page I/O error ?\n");
			return U_FAIL;
		}
	}

	dev->st.pageReadCount++;

	return U_SUCC;
}

static URET femu_ReadPageSpare(uffs_Device *dev, u32 block, u32 pageNum, u8 *spare, int ofs, int len)
{
	int nread;
	int pos;
	int pgSize, pgdSize, spSize, blks, blkPgs, blkSize;
	femu_private *emu;
	emu = (femu_private *)(dev->_private);
//	printf("-------femu_ReadPageSpare------\n");
	if (!emu || !(emu->fp)) return U_FAIL;

	pgSize = dev->attr.page_data_size + dev->attr.spare_size;
	pgdSize = dev->attr.page_data_size;
	spSize = dev->attr.spare_size;
	blkPgs = dev->attr.pages_per_block;
	blks = dev->attr.total_blocks;
	blkSize = dev->attr.block_data_size;

//	printf("RS: %d/%d, size %d\n", block, pageNum, len);

	if(ofs + len > spSize) {
		printf("femu: read page spare out of range!\n");
		return U_FAIL;
	}

	if(spare) {
		pos = (block*blkPgs + pageNum) * (pgdSize + spSize) + conf_pages_data_size + ofs;
		if(my_fseek(emu->fp, pos, SEEK_SET) != 0) {
			printf("femu: seek to %d fail!\n", pos);
			return U_FAIL;
		}
		nread= my_fread(spare, 1, len, emu->fp);

		if(nread != len) {
			printf("femu: read spare I/O error ?\n");
			return U_FAIL;
		}
	}

	dev->st.spareReadCount++;

	return U_SUCC;
}



static URET femu_EraseBlock(uffs_Device *dev, u32 blockNumber)
{
	int i;
	u8 * pg = em_page_buf;
	int pgSize, pgdSize, spSize, blks, blkPgs, blkSize;
	femu_private *emu;
	emu = (femu_private *)(dev->_private);
//	printf("-------femu_EraseBlock------\n");
	if (!emu || !(emu->fp)) return U_FAIL;

	pgSize = dev->attr.page_data_size + dev->attr.spare_size;
	pgdSize = dev->attr.page_data_size;
	spSize = dev->attr.spare_size;
	blkPgs = dev->attr.pages_per_block;
	blks = dev->attr.total_blocks;
	blkSize = dev->attr.block_data_size;

	//printf("femu: erase block %d\n", blockNumber);

	if((int)blockNumber >= blks) {
		printf("Attempt to erase non-existant block %d\n",blockNumber);
		return U_FAIL;
	}
	else {
		//clear this block monitors
#ifdef EM_MONITOR_WRITE
		memset(em_monitor_page + (blockNumber * blkPgs),
			0,
			blkPgs * sizeof(u8));
		memset(em_monitor_spare + (blockNumber * blkPgs),
			0,
			blkPgs * sizeof(u8));
#endif //#ifdef EM_MONITOR_WRITE

		memset(pg, 0xff, (pgdSize + spSize));

		my_fseek(emu->fp, blockNumber * blkPgs * (pgdSize + spSize), SEEK_SET);

		for(i = 0; i < blkPgs; i++)	{
			my_fwrite(pg, 1, (pgdSize + spSize), emu->fp);
		}
        //printf("femu: after erase block %d\n", blockNumber);
		my_fflush(emu->fp);
		dev->st.blockEraseCount++;

		return U_SUCC;
	}

}

static URET femu_ResetFlash(uffs_Device *dev)
{
    printf("-------femu_ResetFlash------\n");
	return U_SUCC;
}

static UBOOL femu_IsBlockBad(uffs_Device *dev, u32 block)
{
	//this function is called when formating flash device

	unsigned char mark;
	mark = 0;
//    printf("-------femu_IsBlockBad------\n");

	femu_ReadPageSpare(dev, block, 0, &mark, 5, 1);
	if (mark == 0xff) {
		femu_ReadPageSpare(dev, block, 1, &mark, 5, 1);
		if (mark == 0xff) {
			return U_FALSE;
		}
	}

	return U_TRUE;
}

/////////////////////////////////////////////////////////////////////////////////

static uffs_DevOps emu_DevOps = {
	femu_ResetFlash,
	femu_IsBlockBad,
	NULL, /* MarkBadBlock */
	femu_EraseBlock,
	femu_WritePage,
	femu_WritePageData,
	femu_WritePageSpare,
	femu_ReadPage,
	femu_ReadPageData,
	femu_ReadPageSpare,
};


static URET femu_initDevice(uffs_Device *dev)
{
	uffs_Perror(UFFS_ERR_NORMAL, PFX "femu device init.\n");

#ifdef USE_NATIVE_MEMORY_ALLOCATOR
	if (uffs_initNativeMemAllocator(dev) != U_SUCC) {
		uffs_Perror(UFFS_ERR_SERIOUS, PFX"Init native memory allocator fail!\n");
		return U_FAIL;
	}
#endif
	dev->attr.dev_type =	UFFS_DEV_EMU;			/* dev_type */
	dev->attr.maker = conf_sim_manid;				/* simulate manufacture ID */
	dev->attr.id = 0xe3;							/* chip id, can be ignored. */
	dev->attr.total_blocks = conf_total_blocks;			/* total blocks */
	dev->attr.block_data_size = BLOCK_DATA_SIZE;	/* block data size */
	dev->attr.page_data_size = conf_pages_data_size;		/* page data size */
	dev->attr.spare_size = conf_pages_spare_size;			/* page spare size */
	dev->attr.pages_per_block = conf_pages_per_block;	/* pages per block */
	dev->attr.block_status_offs = 5;                /* block status offset is 5th byte in spare */
	dev->ops = &emu_DevOps;							/* EMU device operations */

#ifdef EM_MONITOR_WRITE
	em_monitor_page = (u8 *) FsKmalloc(conf_total_blocks * conf_pages_per_block);
	if (!em_monitor_page) return U_FAIL;
	em_monitor_spare = (u8 *) FsKmalloc(conf_total_blocks * conf_pages_per_block);
	if (!em_monitor_spare) return U_FAIL;
#endif //EM_MONITOR_WRITE

	CheckInit(dev);

	return U_SUCC;
}

static void femu_printStatistic(uffs_Device *dev)
{
	uffs_stat *s;
	s = &(dev->st);

	printf("-----------statistics-----------\n");
	printf("Block Erased: %d\n", s->blockEraseCount);
	printf("Write Page:   %d\n", s->pageWriteCount);
	printf("Write Spare:  %d\n", s->spareWriteCount);
	printf("Read Page:    %d\n", s->pageReadCount);
	printf("Read Spare:   %d\n", s->spareReadCount);
	printf("Disk total:   %d\n", uffs_GetDeviceTotal(dev));
	printf("Disk Used:    %d\n", uffs_GetDeviceUsed(dev));
	printf("Disk Free:    %d\n", uffs_GetDeviceFree(dev));
}

static URET femu_releaseDevice(uffs_Device *dev)
{
	femu_private *emu;
#ifdef USE_NATIVE_MEMORY_ALLOCATOR
	int count;
#endif

	uffs_Perror(UFFS_ERR_NORMAL, PFX "femu device release.\n");

	emu = (femu_private *)(dev->_private);

	emu->initCount--;
	if(emu->initCount == 0) {
		if (emu->fp) {
			my_fclose(emu->fp);
			emu->fp = NULL;
		}

		femu_printStatistic(dev);
		memset(emu, 0, sizeof(femu_private));

#ifdef USE_NATIVE_MEMORY_ALLOCATOR
		count = uffs_releaseNativeMemAllocator(dev);
		if (count < 0) {
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"Release native memory allocator fail!\n");
		}
		else if (count > 0) {
			uffs_Perror(UFFS_ERR_NORMAL, PFX"Find %d block memory leak!\n", count);
		}
#endif

#ifdef EM_MONITOR_WRITE
		if (em_monitor_page) FsKfree(em_monitor_page);
		if (em_monitor_spare) FsKfree(em_monitor_spare);
#endif //EM_MONITOR_WRITE
	}

	return U_SUCC;
}

extern "C" void femu_init_uffs_device(uffs_Device *dev);
void femu_init_uffs_device(uffs_Device *dev)
{
	dev->Init = femu_initDevice;
	dev->Release = femu_releaseDevice;
	dev->_private = (void *)(&g_femu_priv);
}

/////////////////////////////////////////////////////////////////////////////////
